From 63915360870997008d4a4d127ee8efd5dc60a638 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 31 Oct 2014 12:25:40 -0700 Subject: [PATCH] Implement overrides via local cargo configuration This is an implementation of overriding native dependencies and passing aribtrary metadata and such. --- src/cargo/ops/cargo_clean.rs | 3 +- src/cargo/ops/cargo_compile.rs | 87 +++++++---- src/cargo/ops/cargo_rustc/context.rs | 14 +- src/cargo/ops/cargo_rustc/custom_build.rs | 173 +++++++++++----------- src/cargo/ops/cargo_rustc/mod.rs | 29 ++-- src/cargo/ops/mod.rs | 1 + tests/test_cargo_compile_custom_build.rs | 56 ++++++- 7 files changed, 226 insertions(+), 137 deletions(-) diff --git a/src/cargo/ops/cargo_clean.rs b/src/cargo/ops/cargo_clean.rs index 0560225a3..7b3aa0ae6 100644 --- a/src/cargo/ops/cargo_clean.rs +++ b/src/cargo/ops/cargo_clean.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::io::fs::{mod, PathExtensions}; use core::{MultiShell, PackageSet}; @@ -49,7 +50,7 @@ pub fn clean(manifest_path: &Path, opts: &mut CleanOptions) -> CargoResult<()> { let pkgs = PackageSet::new([]); let cx = try!(Context::new("compile", &resolve, &srcs, &pkgs, &mut cfg, Layout::at(root.get_absolute_target_dir()), - None, &pkg)); + None, &pkg, HashMap::new())); // And finally, clean everything out! for target in pkg.get_targets().iter() { diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 2947efa4b..8b25881fe 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -28,7 +28,7 @@ use std::collections::HashMap; use core::registry::PackageRegistry; use core::{MultiShell, Source, SourceId, PackageSet, Package, Target, PackageId}; use core::resolver; -use ops; +use ops::{mod, BuildOutput}; use sources::{PathSource}; use util::config::{Config, ConfigValue}; use util::{CargoResult, Wrap, config, internal, human, ChainError, profile}; @@ -138,12 +138,12 @@ pub fn compile_pkg(package: &Package, options: &mut CompileOptions) let ret = { let _p = profile::start("compiling"); - try!(scrape_target_config(&config, &user_configs)); + let lib_overrides = try!(scrape_target_config(&config, &user_configs)); try!(ops::compile_targets(env.as_slice(), targets.as_slice(), to_build, &PackageSet::new(packages.as_slice()), &resolve_with_overrides, &sources, - &config)) + &config, lib_overrides)) }; return Ok(ret); @@ -175,41 +175,68 @@ fn source_ids_from_config(configs: &HashMap, fn scrape_target_config(config: &Config, configs: &HashMap) - -> CargoResult<()> { + -> CargoResult> { let target = match configs.find_equiv("target") { - None => return Ok(()), + None => return Ok(HashMap::new()), Some(target) => try!(target.table().chain_error(|| { internal("invalid configuration for the key `target`") })), }; - let target = match config.target() { - None => target, - Some(triple) => match target.find_equiv(triple) { - None => return Ok(()), - Some(target) => try!(target.table().chain_error(|| { - internal(format!("invalid configuration for the key \ - `target.{}`", triple)) - })), - }, + let triple = config.target().unwrap_or(config.rustc_host()).to_string(); + let target = match target.find(&triple) { + None => return Ok(HashMap::new()), + Some(target) => try!(target.table().chain_error(|| { + internal(format!("invalid configuration for the key \ + `target.{}`", triple)) + })), }; - match target.find_equiv("ar") { - None => {} - Some(ar) => { - config.set_ar(try!(ar.string().chain_error(|| { - internal("invalid configuration for key `ar`") - })).ref0().to_string()); - } - } - - match target.find_equiv("linker") { - None => {} - Some(linker) => { - config.set_linker(try!(linker.string().chain_error(|| { - internal("invalid configuration for key `ar`") - })).ref0().to_string()); + let mut ret = HashMap::new(); + for (k, v) in target.iter() { + match k.as_slice() { + "ar" | "linker" => { + let v = try!(v.string().chain_error(|| { + internal(format!("invalid configuration for key `{}`", k)) + })).ref0().to_string(); + if k.as_slice() == "linker" { + config.set_linker(v); + } else { + config.set_ar(v); + } + } + lib_name => { + let table = try!(v.table().chain_error(|| { + internal(format!("invalid configuration for the key \ + `target.{}.{}`", triple, lib_name)) + })); + let mut output = BuildOutput { + library_paths: Vec::new(), + library_links: Vec::new(), + metadata: Vec::new(), + }; + for (k, v) in table.iter() { + let v = try!(v.string().chain_error(|| { + internal(format!("invalid configuration for the key \ + `target.{}.{}.{}`", triple, lib_name, + k)) + })).val0(); + if k.as_slice() == "rustc-flags" { + let whence = format!("in `target.{}.{}.rustc-flags`", + triple, lib_name); + let whence = whence.as_slice(); + let (paths, links) = try!( + BuildOutput::parse_rustc_flags(v.as_slice(), whence) + ); + output.library_paths.extend(paths.into_iter()); + output.library_links.extend(links.into_iter()); + } else { + output.metadata.push((k.to_string(), v.to_string())); + } + } + ret.insert(lib_name.to_string(), output); + } } } - Ok(()) + Ok(ret) } diff --git a/src/cargo/ops/cargo_rustc/context.rs b/src/cargo/ops/cargo_rustc/context.rs index 4b25688a8..49b1c63e4 100644 --- a/src/cargo/ops/cargo_rustc/context.rs +++ b/src/cargo/ops/cargo_rustc/context.rs @@ -1,12 +1,13 @@ use std::collections::HashSet; use std::collections::hash_map::{HashMap, Occupied, Vacant}; use std::str; +use std::sync::{Arc, Mutex}; use core::{SourceMap, Package, PackageId, PackageSet, Resolve, Target}; use util::{mod, CargoResult, ChainError, internal, Config, profile}; use util::human; -use super::{Kind, KindHost, KindTarget, Compilation}; +use super::{Kind, KindHost, KindTarget, Compilation, BuildOutput}; use super::layout::{Layout, LayoutProxy}; #[deriving(Show)] @@ -21,6 +22,7 @@ pub struct Context<'a, 'b: 'a> { pub resolve: &'a Resolve, pub sources: &'a SourceMap<'b>, pub compilation: Compilation, + pub native_libs: Arc>>, env: &'a str, host: Layout, @@ -37,7 +39,8 @@ impl<'a, 'b: 'a> Context<'a, 'b> { pub fn new(env: &'a str, resolve: &'a Resolve, sources: &'a SourceMap<'b>, deps: &'a PackageSet, config: &'b Config<'b>, host: Layout, target: Option, - root_pkg: &Package) + root_pkg: &Package, + native_libs: HashMap) -> CargoResult> { let (target_dylib, target_exe) = try!(Context::filename_parts(config.target())); @@ -63,6 +66,7 @@ impl<'a, 'b: 'a> Context<'a, 'b> { host_dylib: host_dylib, requirements: HashMap::new(), compilation: Compilation::new(root_pkg), + native_libs: Arc::new(Mutex::new(native_libs)), }) } @@ -230,12 +234,10 @@ impl<'a, 'b: 'a> Context<'a, 'b> { None => return vec!(), Some(deps) => deps, }; - deps.map(|pkg_id| self.get_package(pkg_id)) - .filter_map(|pkg| { + deps.map(|pkg_id| self.get_package(pkg_id)).filter_map(|pkg| { pkg.get_targets().iter().find(|&t| self.is_relevant_target(t)) .map(|t| (pkg, t)) - }) - .collect() + }).collect() } /// Gets a package for the given package id. diff --git a/src/cargo/ops/cargo_rustc/custom_build.rs b/src/cargo/ops/cargo_rustc/custom_build.rs index 55c022327..fa6373642 100644 --- a/src/cargo/ops/cargo_rustc/custom_build.rs +++ b/src/cargo/ops/cargo_rustc/custom_build.rs @@ -1,5 +1,5 @@ -use std::io::{fs, BufferedReader, BufReader, USER_RWX}; -use std::io::fs::{File, PathExtensions}; +use std::io::{fs, BufReader, USER_RWX}; +use std::io::fs::PathExtensions; use core::{Package, Target}; use util::{CargoResult, CargoError, human}; @@ -8,10 +8,21 @@ use util::{internal, ChainError}; use super::job::Work; use super::{process, KindHost, Context}; +/// Contains the parsed output of a custom build script. +#[deriving(Clone)] +pub struct BuildOutput { + /// Paths to pass to rustc with the `-L` flag + pub library_paths: Vec, + /// Names and link kinds of libraries, suitable for the `-l` flag + pub library_links: Vec, + /// Metadata to pass to the immediate dependencies + pub metadata: Vec<(String, String)>, +} + /// Prepares a `Work` that executes the target as a custom build script. pub fn prepare_execute_custom_build(pkg: &Package, target: &Target, - cx: &mut Context) - -> CargoResult { + cx: &mut Context) + -> CargoResult { let layout = cx.layout(pkg, KindHost); let script_output = layout.build(pkg); let build_output = layout.build_out(pkg); @@ -35,62 +46,50 @@ pub fn prepare_execute_custom_build(pkg: &Package, target: &Target, match cx.resolve.features(pkg.get_package_id()) { Some(features) => { for feat in features.iter() { - let feat = feat.as_slice().chars() - .map(|c| c.to_uppercase()) - .map(|c| if c == '-' {'_'} else {c}) - .collect::(); - p = p.env(format!("CARGO_FEATURE_{}", feat).as_slice(), Some("1")); + p = p.env(format!("CARGO_FEATURE_{}", + super::envify(feat.as_slice())).as_slice(), + Some("1")); } } None => {} } - // building the list of all possible `build/$pkg/output` files - // whether they exist or not will be checked during the work - let command_output_files = { - let layout = cx.layout(pkg, KindHost); - cx.dep_targets(pkg).iter().map(|&(pkg, _)| { - layout.build(pkg).join("output") - }).collect::>() + // Gather the set of native dependencies that this package has + let lib_deps = { + cx.dep_targets(pkg).iter().filter_map(|&(pkg, _)| { + pkg.get_manifest().get_links() + }).map(|s| s.to_string()).collect::>() }; + let native_libs = cx.native_libs.clone(); + // Building command let pkg = pkg.to_string(); let work = proc(desc_tx: Sender) { - desc_tx.send_opt(build_output.display().to_string()).ok(); if !build_output.exists() { - try!(fs::mkdir(&build_output, USER_RWX) - .chain_error(|| { - internal("failed to create build output directory for build command") - })) + try!(fs::mkdir(&build_output, USER_RWX).chain_error(|| { + internal("failed to create build output directory for \ + build command") + })) } - // loading each possible custom build output file in order to get their metadata - let _metadata = { - let mut metadata = Vec::new(); - - for flags_file in command_output_files.into_iter() { - match File::open(&flags_file) { - Ok(flags) => { - let flags = try!(CustomBuildCommandOutput::parse( - BufferedReader::new(flags), pkg.as_slice())); - metadata.extend(flags.metadata.into_iter()); - }, - Err(_) => () // the file doesn't exist, probably means that this pkg - // doesn't have a build command + // loading each possible custom build output file in order to get their + // metadata + let mut p = p; + { + let native_libs = native_libs.lock(); + for dep in lib_deps.iter() { + for &(ref key, ref value) in (*native_libs)[*dep].metadata.iter() { + p = p.env(format!("DEP_{}_{}", + super::envify(dep.as_slice()), + super::envify(key.as_slice())).as_slice(), + Some(value.as_slice())); } } + } - metadata - }; - - // TODO: ENABLE THIS CODE WHEN `links` IS ADDED - /*let mut p = p; - for (key, value) in metadata.into_iter() { - p = p.env(format!("DEP_{}_{}", PUT LINKS VALUES HERE, value), value); - }*/ - + desc_tx.send_opt(p.to_string()).ok(); let output = try!(p.exec_with_output().map_err(|mut e| { e.msg = format!("Failed to run custom build command for `{}`\n{}", pkg, e.msg); @@ -98,7 +97,7 @@ pub fn prepare_execute_custom_build(pkg: &Package, target: &Target, })); // parsing the output of the custom build script to check that it's correct - try!(CustomBuildCommandOutput::parse(BufReader::new(output.output.as_slice()), + try!(BuildOutput::parse(BufReader::new(output.output.as_slice()), pkg.as_slice())); // writing the output to the right directory @@ -113,23 +112,14 @@ pub fn prepare_execute_custom_build(pkg: &Package, target: &Target, Ok(work) } -/// Contains the parsed output of a custom build script. -pub struct CustomBuildCommandOutput { - /// Paths to pass to rustc with the `-L` flag - pub library_paths: Vec, - /// Names and link kinds of libraries, suitable for the `-l` flag - pub library_links: Vec, - /// Metadata to pass to the immediate dependencies - pub metadata: Vec<(String, String)>, -} - -impl CustomBuildCommandOutput { +impl BuildOutput { // Parses the output of a script. // The `pkg_name` is used for error messages. - pub fn parse(mut input: B, pkg_name: &str) -> CargoResult { + pub fn parse(mut input: B, pkg_name: &str) -> CargoResult { let mut library_paths = Vec::new(); let mut library_links = Vec::new(); let mut metadata = Vec::new(); + let whence = format!("build script of `{}`", pkg_name); for line in input.lines() { // unwrapping the IoResult @@ -153,46 +143,59 @@ impl CustomBuildCommandOutput { let (key, value) = match (key, value) { (Some(a), Some(b)) => (a, b), // line started with `cargo:` but didn't match `key=value` - _ => return Err(human(format!("Wrong output for the custom\ - build script of `{}`:\n`{}`", pkg_name, line))) + _ => return Err(human(format!("Wrong output in {}: `{}`", + whence, line))) }; if key == "rustc-flags" { - // TODO: some arguments (like paths) may contain spaces - let mut flags_iter = value.words(); - loop { - let flag = match flags_iter.next() { - Some(f) => f, - None => break - }; - if flag != "-l" && flag != "-L" { - return Err(human(format!("Only `-l` and `-L` flags are allowed \ - in build script of `{}`:\n`{}`", - pkg_name, value))) - } - let value = match flags_iter.next() { - Some(v) => v, - None => return Err(human(format!("Flag in rustc-flags has no value\ - in build script of `{}`:\n`{}`", - pkg_name, value))) - }; - match flag { - "-l" => library_links.push(value.to_string()), - "-L" => library_paths.push(Path::new(value)), - - // was already checked above - _ => return Err(human("only -l and -L flags are allowed")) - }; - } + let whence = whence.as_slice(); + let (libs, links) = try!( + BuildOutput::parse_rustc_flags(value, whence) + ); + library_links.extend(links.into_iter()); + library_paths.extend(libs.into_iter()); } else { metadata.push((key.to_string(), value.to_string())) } } - Ok(CustomBuildCommandOutput { + Ok(BuildOutput { library_paths: library_paths, library_links: library_links, metadata: metadata, }) } + + pub fn parse_rustc_flags(value: &str, whence: &str) + -> CargoResult<(Vec, Vec)> { + // TODO: some arguments (like paths) may contain spaces + let value = value.trim(); + let mut flags_iter = value.words(); + let (mut library_links, mut library_paths) = (Vec::new(), Vec::new()); + loop { + let flag = match flags_iter.next() { + Some(f) => f, + None => break + }; + if flag != "-l" && flag != "-L" { + return Err(human(format!("Only `-l` and `-L` flags are allowed \ + in {}: `{}`", + whence, value))) + } + let value = match flags_iter.next() { + Some(v) => v, + None => return Err(human(format!("Flag in rustc-flags has no value\ + in {}: `{}`", + whence, value))) + }; + match flag { + "-l" => library_links.push(value.to_string()), + "-L" => library_paths.push(Path::new(value)), + + // was already checked above + _ => return Err(human("only -l and -L flags are allowed")) + }; + } + Ok((library_paths, library_links)) + } } diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 4691fdc70..363ebd875 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::{HashSet, HashMap}; use std::dynamic_lib::DynamicLibrary; use std::io::{fs, BufferedReader, USER_RWX}; use std::io::fs::{File, PathExtensions}; @@ -8,7 +8,6 @@ use core::{SourceMap, Package, PackageId, PackageSet, Target, Resolve}; use util::{mod, CargoResult, ProcessBuilder, CargoError, human, caused_human}; use util::{Require, Config, internal, ChainError, Fresh, profile, join_paths}; -use self::custom_build::CustomBuildCommandOutput; use self::job::{Job, Work}; use self::job_queue as jq; use self::job_queue::JobQueue; @@ -18,6 +17,7 @@ pub use self::context::Context; pub use self::context::{PlatformPlugin, PlatformPluginAndTarget}; pub use self::context::{PlatformRequirement, PlatformTarget}; pub use self::layout::{Layout, LayoutProxy}; +pub use self::custom_build::BuildOutput; mod context; mod compilation; @@ -76,7 +76,8 @@ fn uniq_target_dest<'a>(targets: &[&'a Target]) -> Option<&'a str> { pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package, deps: &PackageSet, resolve: &'a Resolve, sources: &'a SourceMap, - config: &'a Config<'a>) + config: &'a Config<'a>, + lib_overrides: HashMap) -> CargoResult { if targets.is_empty() { return Ok(Compilation::new(pkg)) @@ -94,7 +95,8 @@ pub fn compile_targets<'a>(env: &str, targets: &[&'a Target], pkg: &'a Package, }); let mut cx = try!(Context::new(env, resolve, sources, deps, config, - host_layout, target_layout, pkg)); + host_layout, target_layout, pkg, + lib_overrides)); let mut queue = JobQueue::new(cx.resolve, deps, cx.config); // First ensure that the destination directory exists @@ -305,11 +307,9 @@ fn compile_custom_old(pkg: &Package, cmd: &str, match cx.resolve.features(pkg.get_package_id()) { Some(features) => { for feat in features.iter() { - let feat = feat.as_slice().chars() - .map(|c| c.to_uppercase()) - .map(|c| if c == '-' {'_'} else {c}) - .collect::(); - p = p.env(format!("CARGO_FEATURE_{}", feat).as_slice(), Some("1")); + p = p.env(format!("CARGO_FEATURE_{}", + envify(feat.as_slice())).as_slice(), + Some("1")); } } None => {} @@ -379,7 +379,7 @@ fn rustc(package: &Package, target: &Target, // list of `-l` flags to pass to rustc coming from custom build scripts let additional_library_links = match File::open(&command_output_file) { Ok(f) => { - let flags = try!(CustomBuildCommandOutput::parse( + let flags = try!(BuildOutput::parse( BufferedReader::new(f), name.as_slice())); additional_library_paths.extend(flags.library_paths.iter().map(|p| p.clone())); @@ -396,7 +396,7 @@ fn rustc(package: &Package, target: &Target, // doesn't have a build command }; - let flags = try!(CustomBuildCommandOutput::parse( + let flags = try!(BuildOutput::parse( BufferedReader::new(flags), name.as_slice())); additional_library_paths.extend(flags.library_paths.iter().map(|p| p.clone())); } @@ -720,3 +720,10 @@ fn each_dep<'a>(pkg: &Package, cx: &'a Context, f: |&'a Package|) { } } } + +fn envify(s: &str) -> String { + s.chars() + .map(|c| c.to_uppercase()) + .map(|c| if c == '-' {'_'} else {c}) + .collect() +} diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index 33f165102..d65629986 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -5,6 +5,7 @@ pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind, rustc_ve pub use self::cargo_rustc::{KindTarget, KindHost, Context, LayoutProxy}; pub use self::cargo_rustc::{PlatformRequirement, PlatformTarget}; pub use self::cargo_rustc::{PlatformPlugin, PlatformPluginAndTarget}; +pub use self::cargo_rustc::{BuildOutput}; pub use self::cargo_run::run; pub use self::cargo_new::{new, NewOptions}; pub use self::cargo_doc::{doc, DocOptions}; diff --git a/tests/test_cargo_compile_custom_build.rs b/tests/test_cargo_compile_custom_build.rs index db4613abd..9997051ed 100644 --- a/tests/test_cargo_compile_custom_build.rs +++ b/tests/test_cargo_compile_custom_build.rs @@ -129,9 +129,8 @@ test!(custom_build_script_wrong_rustc_flags { assert_that(p.cargo_process("build"), execs().with_status(101) .with_stderr(format!("\ -Only `-l` and `-L` flags are allowed in build script of `foo v0.5.0 ({})`: -`-aaa -bbb -`", +Only `-l` and `-L` flags are allowed in build script of `foo v0.5.0 ({})`: \ +`-aaa -bbb`", p.url()))); }) @@ -206,7 +205,6 @@ not have a custom build script ")); }) - test!(links_duplicates { let p = project("foo") .file("Cargo.toml", r#" @@ -244,3 +242,53 @@ linked to by one package ")); }) +test!(overrides_and_links { + let (_, target) = ::cargo::ops::rustc_version().unwrap(); + + let p = project("foo") + .file("Cargo.toml", r#" + [project] + name = "foo" + version = "0.5.0" + authors = [] + build = "build.rs" + + [dependencies.a] + path = "a" + "#) + .file("src/lib.rs", "") + .file("build.rs", r#" + use std::os; + fn main() { + assert_eq!(os::getenv("DEP_FOO_FOO").unwrap().as_slice(), "bar"); + assert_eq!(os::getenv("DEP_FOO_BAR").unwrap().as_slice(), "baz"); + } + "#) + .file(".cargo/config", format!(r#" + [target.{}.foo] + rustc-flags = "-l foo -L bar" + foo = "bar" + bar = "baz" + "#, target).as_slice()) + .file("a/Cargo.toml", r#" + [project] + name = "a" + version = "0.5.0" + authors = [] + links = "foo" + build = "build.rs" + "#) + .file("a/src/lib.rs", "") + .file("a/build.rs", "not valid rust code"); + + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(0) + .with_stdout("\ +Compiling a v0.5.0 (file://[..]) + Running `rustc [..] --crate-name a [..]` +Compiling foo v0.5.0 (file://[..]) + Running `rustc build.rs [..]` + Running `rustc [..] --crate-name foo [..]` +")); +}) + -- 2.30.2